package io.openkunlun.javadsl.springboot3.server;

import io.openkunlun.javadsl.server.DaprHandler;
import io.openkunlun.javarpc.RpcImplement;
import io.openkunlun.javarpc.RpcStub;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DaprHandlerFactoryBean implements FactoryBean<DaprHandler>, ApplicationContextAware {

    private final Class<DaprHandler> type;
    private final List<Field> fields;
    private final List<Class<?>> args;
    private ApplicationContext applicationContext;

    /**
     * @param type
     * @param fields
     */
    public DaprHandlerFactoryBean(Class<DaprHandler> type, List<Field> fields) {
        this.args = fields.stream().map(Field::getType).collect(Collectors.toList());
        this.fields = fields;
        this.type = type;
    }

    @Override
    public DaprHandler getObject() throws Exception {
        List<Object> params = fields.stream().map(this::applicationContextBean).collect(Collectors.toList());
        DaprHandler instance = this.type.getDeclaredConstructor(args.toArray(new Class<?>[]{})).newInstance(params.toArray(new Object[]{}));
        applicationContext.getAutowireCapableBeanFactory().autowireBean(instance);
        return instance;
    }

    @Override
    public Class<?> getObjectType() {
        return type;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private Object applicationContextBean(Field field) {
        Class<?> clazz = field.getType();
        RpcImplement implement = field.getAnnotation(RpcImplement.class);
        if (implement != null) {
            Map<String, ?> beans = applicationContext.getBeansOfType(clazz);
            for (Object t : beans.values()) {
                if (!(t instanceof RpcStub)) {
                    return t;
                }
            }
            throw new NoSuchBeanDefinitionException(clazz);
        } else {
            return applicationContext.getBean(clazz);
        }
    }
}
